package cn.cyh.queue_service.service.impl;

import cn.cyh.queue_service.common.Constants;
import cn.cyh.queue_service.common.Result;
import cn.cyh.queue_service.common.ResultCode;
import cn.cyh.queue_service.config.ApplicationConfig;
import cn.cyh.queue_service.config.FaceRecognitionProperties;
import cn.cyh.queue_service.controller.FaceProxyController;
import cn.cyh.queue_service.exception.BusinessException;
import cn.cyh.queue_service.exception.ExternalServiceException;
import cn.cyh.queue_service.mapper.QueueMapper;
import cn.cyh.queue_service.mapper.ServiceTypeMapper;
import cn.cyh.queue_service.mapper.WindowMapper;
import cn.cyh.queue_service.model.dto.*;
import cn.cyh.queue_service.model.entity.Queue;
import cn.cyh.queue_service.model.entity.ServiceType;
import cn.cyh.queue_service.model.entity.Window;
import cn.cyh.queue_service.model.vo.QueueVO;
import cn.cyh.queue_service.mq.producer.EventProducer;
import cn.cyh.queue_service.service.NoticeService;
import cn.cyh.queue_service.service.QueueService;
import cn.cyh.queue_service.service.StatisticsService;
import cn.cyh.queue_service.service.WindowService;
import cn.cyh.queue_service.websocket.*;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 队列服务实现类
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class QueueServiceImpl extends ServiceImpl<QueueMapper, Queue> implements QueueService {
    
    private final QueueMapper queueMapper;
    private final ServiceTypeMapper serviceTypeMapper;
    private final WindowMapper windowMapper;
    private final WindowService windowService;
    private final RedisTemplate<String, Object> redisTemplate;
    private final RabbitTemplate rabbitTemplate;
    private final CallScreenWebSocketHandler callScreenWebSocketHandler;
    private final QueueStatusWebSocketHandler queueStatusWebSocketHandler;
    private final RestTemplate restTemplate;
    private final NoticeService noticeService;
    private final StatisticsService statisticsService;
    private final EventProducer eventProducer;
    private final FaceProxyController faceProxyController;
    private final ApplicationConfig applicationConfig;
    private final FaceRecognitionProperties faceProperties;
    
    @Value("${queue.service.avg-time:5}")
    private int avgServiceTime; // 平均服务时间（分钟）
    
    @Value("${face.recognition.base-url}")
    private String faceProxyUrl;
    
    @Value("${face.recognition.max-retries:3}")
    private int maxRetries;
    
    @Value("${face.recognition.retry-delay:1000}")
    private long retryDelay;
    
    /**
     * 人脸特征相似度阈值
     */
    private final float similarityThreshold;
    
    /**
     * 构造函数
     */
    @Autowired
    public QueueServiceImpl(
            QueueMapper queueMapper, ServiceTypeMapper serviceTypeMapper,
            WindowMapper windowMapper, WindowService windowService, RedisTemplate<String, Object> redisTemplate, RabbitTemplate rabbitTemplate, CallScreenWebSocketHandler callScreenWebSocketHandler,
            QueueStatusWebSocketHandler queueStatusWebSocketHandler,
            RestTemplate restTemplate, NoticeService noticeService,
            StatisticsService statisticsService,
            EventProducer eventProducer,
            FaceProxyController faceProxyController,
            ApplicationConfig applicationConfig,
            FaceRecognitionProperties faceProperties) {
        this.queueMapper = queueMapper;
        this.serviceTypeMapper = serviceTypeMapper;
        this.windowMapper = windowMapper;
        this.windowService = windowService;
        this.redisTemplate = redisTemplate;
        this.rabbitTemplate = rabbitTemplate;
        this.callScreenWebSocketHandler = callScreenWebSocketHandler;
        this.queueStatusWebSocketHandler = queueStatusWebSocketHandler;
        this.restTemplate = restTemplate;
        this.noticeService = noticeService;
        this.statisticsService = statisticsService;
        this.eventProducer = eventProducer;
        this.faceProxyController = faceProxyController;
        this.applicationConfig = applicationConfig;
        this.faceProperties = faceProperties;
        
        // 从配置中获取人脸特征相似度阈值
        this.similarityThreshold = applicationConfig.getQueue().getFaceVerification().getVerificationThreshold();
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public TakeNumberResponse takeNumber(TakeNumberRequest request) {
        // 验证业务类型是否存在
        ServiceType serviceType = serviceTypeMapper.selectById(request.getServiceTypeId());
        if (serviceType == null) {
            throw new BusinessException(ResultCode.BUSINESS_TYPE_NOT_FOUND);
        }
        
        // 验证业务类型是否启用
        if (serviceType.getStatus() == 0) {
            throw new BusinessException(ResultCode.BUSINESS_TYPE_DISABLED);
        }
        
        // 验证人脸特征数据（如果需要）
//        if (serviceType.getRequireFace() == 1 && StrUtil.isBlank(request.getFaceFeature())) {
//            throw new BusinessException(ResultCode.FACE_FEATURE_REQUIRED);
//        }
        
        // 生成票号
        String ticketNumber = generateTicketNumber(serviceType);
        
        // 创建队列记录
        Queue queue = Queue.builder()
                .ticketNumber(ticketNumber)
                .serviceTypeId(request.getServiceTypeId())
                .status("WAITING")
                .faceFeature(request.getFaceFeature())
                .isVerified(serviceType.getRequireFace() == 0 ? 1 : 0) // 如果不需要人脸验证，默认为已验证
                .createTime(LocalDateTime.now())
                .build();
        
        // 保存记录
        save(queue);
        
        // 更新等待人数
        int waitingCount = getWaitingCountByServiceType(request.getServiceTypeId());
        
        // 计算预估等待时间
        int estimatedWaitTime = calculateEstimatedWaitTime(request.getServiceTypeId(), waitingCount);
        
        // 发布队列状态更新消息
//        publishQueueStatusUpdate(request.getServiceTypeId(), serviceType.getName(), ticketNumber, waitingCount, estimatedWaitTime);
        
        // 构建响应
        return createTakeNumberResponse(queue, serviceType, waitingCount);
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public QueueVO callNext(CallNextRequest request) {
        // 验证窗口是否存在
        Window window = windowMapper.selectById(request.getWindowId());
        if (window == null) {
            throw new BusinessException(ResultCode.WINDOW_NOT_FOUND);
        }
        
        // 验证窗口状态是否开放
        if (!"OPEN".equals(window.getStatus())) {
            throw new BusinessException(ResultCode.WINDOW_NOT_OPEN);
        }
        
        // 检查当前窗口是否有正在处理的业务
        Queue currentQueue = queueMapper.selectCurrentQueueByWindow(request.getWindowId());
        if (currentQueue != null && !Boolean.TRUE.equals(request.getForceTransfer())) {
            throw new BusinessException(ResultCode.WINDOW_HAS_CURRENT_NUMBER);
        } else if (currentQueue != null && Boolean.TRUE.equals(request.getForceTransfer())) {
            // 强制转移，将当前处理的业务标记为已完成
            CompleteServiceRequest completeRequest = new CompleteServiceRequest();
            completeRequest.setTicketNumber(currentQueue.getTicketNumber());
            completeRequest.setWindowId(request.getWindowId());
            completeService(completeRequest);
        }
        
        // 确定使用哪个业务类型进行叫号
        Long serviceTypeId = request.getServiceTypeId() != null ? 
                request.getServiceTypeId() : window.getServiceTypeId();
        
        if (serviceTypeId == null) {
            throw new BusinessException(ResultCode.SERVICE_TYPE_NOT_SPECIFIED);
        }
        
        // 验证业务类型是否存在
        ServiceType serviceType = serviceTypeMapper.selectById(serviceTypeId);
        if (serviceType == null) {
            throw new BusinessException(ResultCode.BUSINESS_TYPE_NOT_FOUND);
        }
        
        // 获取等待中的队列列表（按取号时间排序）
        List<Queue> waitingQueues = queueMapper.selectWaitingQueuesByServiceType(serviceTypeId);
        if (waitingQueues.isEmpty()) {
            throw new BusinessException(ResultCode.NO_WAITING_QUEUE);
        }
        
        // 获取下一个待叫号的队列记录
        Queue nextQueue = waitingQueues.get(0);
        
        // 更新队列状态为叫号中
        LambdaUpdateWrapper<Queue> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(Queue::getId, nextQueue.getId())
                .set(Queue::getStatus, "CALLING")
                .set(Queue::getWindowId, request.getWindowId())
                .set(Queue::getCallTime, LocalDateTime.now());
        
        update(updateWrapper);
        
        // 更新窗口当前票号
        windowService.updateCurrentNumber(request.getWindowId(), nextQueue.getTicketNumber());
        
        // 获取最新的队列记录
        nextQueue = getById(nextQueue.getId());
        
        // 广播叫号消息
        broadcastCallMessage(nextQueue, window, serviceType);
        
        // 发布队列状态更新消息
        int waitingCount = getWaitingCountByServiceType(serviceTypeId);
//        int estimatedWaitTime = calculateEstimatedWaitTime(serviceTypeId, waitingCount);
//        publishQueueStatusUpdate(serviceTypeId, serviceType.getName(), nextQueue.getTicketNumber(), waitingCount, estimatedWaitTime);
        
        // 更新统计数据 - 等待人数
        statisticsService.updateWaitingCount(nextQueue.getServiceTypeId());
        CurrentQueues.sendRefreshData();
        CurrentScreens.sendRefreshData();
        return convertToVO(nextQueue);
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean verifyFace(VerifyRequest request) {
        // 验证票号是否存在
        Queue queue = queueMapper.selectByTicketNumber(request.getTicketNumber());
        if (queue == null) {
            throw new BusinessException(ResultCode.TICKET_NOT_FOUND);
        }
        
        // 验证窗口是否存在
        Window window = windowMapper.selectById(request.getWindowId());
        if (window == null) {
            throw new BusinessException(ResultCode.WINDOW_NOT_FOUND);
        }
        
        // 验证队列状态是否为叫号中
        if (!"CALLING".equals(queue.getStatus())) {
            throw new BusinessException(ResultCode.QUEUE_STATUS_ERROR);
        }
        
        // 验证窗口ID是否匹配
        if (!request.getWindowId().equals(queue.getWindowId())) {
            throw new BusinessException(ResultCode.WINDOW_MISMATCH);
        }
        
        // 验证是否已经验证过
        if (queue.getIsVerified() == 1) {
            return true;
        }
        
        // 验证人脸特征（实际项目中应调用人脸识别服务进行比对）
        boolean verifyResult = verifyFaceFeature(queue.getFaceFeature(), request.getTicketNumber());
        if (!verifyResult) {
            throw new BusinessException(ResultCode.FACE_VERIFY_FAILED);
        }
        
        // 更新验证状态
        LambdaUpdateWrapper<Queue> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(Queue::getId, queue.getId())
                .set(Queue::getIsVerified, 1)
                .set(Queue::getVerifyTime, LocalDateTime.now());
        
        update(updateWrapper);
        
        // 返回验证结果
        return true;
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean startService(String ticketNumber, Long windowId) {
        // 验证票号是否存在
        Queue queue = queueMapper.selectByTicketNumber(ticketNumber);
        if (queue == null) {
            throw new BusinessException(ResultCode.TICKET_NOT_FOUND);
        }
        
        // 验证窗口是否存在
        Window window = windowMapper.selectById(windowId);
        if (window == null) {
            throw new BusinessException(ResultCode.WINDOW_NOT_FOUND);
        }
        
        // 验证队列状态是否为叫号中
        if (!"CALLING".equals(queue.getStatus())) {
            throw new BusinessException(ResultCode.QUEUE_STATUS_ERROR);
        }
        
        // 验证窗口ID是否匹配
        if (!windowId.equals(queue.getWindowId())) {
            throw new BusinessException(ResultCode.WINDOW_MISMATCH);
        }
        
        // 验证是否已验证（如果需要人脸识别）
        ServiceType serviceType = serviceTypeMapper.selectById(queue.getServiceTypeId());
        if (serviceType != null && serviceType.getRequireFace() == 1 && queue.getIsVerified() == 0) {
            throw new BusinessException(ResultCode.FACE_NOT_VERIFIED);
        }
        
        // 更新状态为服务中
        LambdaUpdateWrapper<Queue> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(Queue::getId, queue.getId())
                .set(Queue::getStatus, "SERVING")
                .set(Queue::getStartTime, LocalDateTime.now());
        
        return update(updateWrapper);
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean completeService(CompleteServiceRequest request) {
        // 验证票号是否存在
        Queue queue = queueMapper.selectByTicketNumber(request.getTicketNumber());
        if (queue == null) {
            throw new BusinessException(ResultCode.TICKET_NOT_FOUND);
        }
        
        // 验证窗口是否存在
        Window window = windowMapper.selectById(request.getWindowId());
        if (window == null) {
            throw new BusinessException(ResultCode.WINDOW_NOT_FOUND);
        }
        
        // 验证队列状态
        if (!"CALLING".equals(queue.getStatus()) && !"SERVING".equals(queue.getStatus())) {
            throw new BusinessException(ResultCode.QUEUE_STATUS_ERROR);
        }
        
        // 验证窗口ID是否匹配
        if (!request.getWindowId().equals(queue.getWindowId())) {
            throw new BusinessException(ResultCode.WINDOW_MISMATCH);
        }
        
        // 计算服务时长
        LocalDateTime endTime = LocalDateTime.now();
        LocalDateTime startTime = queue.getStartTime() != null ? queue.getStartTime() : queue.getCallTime();
        int serviceTime = (startTime != null) ? 
                Math.max(1, (int) TimeUnit.SECONDS.toMinutes(endTime.toEpochSecond(java.time.ZoneOffset.UTC) - 
                        startTime.toEpochSecond(java.time.ZoneOffset.UTC))) : 0;
        
        // 计算等待时长
        int waitTime = (queue.getCallTime() != null && queue.getCreateTime() != null) ?
                Math.max(0, (int) TimeUnit.SECONDS.toMinutes(queue.getCallTime().toEpochSecond(java.time.ZoneOffset.UTC) - 
                        queue.getCreateTime().toEpochSecond(java.time.ZoneOffset.UTC))) : 0;
        
        // 更新状态为已完成
        LambdaUpdateWrapper<Queue> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(Queue::getId, queue.getId())
                .set(Queue::getStatus, "COMPLETED")
                .set(Queue::getEndTime, endTime)
                .set(Queue::getServiceTime, serviceTime)
                .set(Queue::getWaitTime, waitTime);
        
        boolean result = update(updateWrapper);
        
        // 清除窗口当前票号
        if (result) {
            windowService.updateCurrentNumber(request.getWindowId(), null);
            
            // 更新业务类型等待人数
            int waitingCount = getWaitingCountByServiceType(queue.getServiceTypeId());
            ServiceType serviceType = serviceTypeMapper.selectById(queue.getServiceTypeId());
            String serviceTypeName = serviceType != null ? serviceType.getName() : "";
            
            // 发布队列状态更新消息
//            publishQueueStatusUpdate(queue.getServiceTypeId(), serviceTypeName, null, waitingCount, 0);
        }
        statisticsService.updateWaitingCount(queue.getServiceTypeId());
        CurrentScreens.sendRefreshData();
        return result;
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean markAsNoShow(String ticketNumber, Long windowId) {
        // 验证票号是否存在
        Queue queue = queueMapper.selectByTicketNumber(ticketNumber);
        if (queue == null) {
            throw new BusinessException(ResultCode.TICKET_NOT_FOUND);
        }
        
        // 验证窗口是否存在
        Window window = windowMapper.selectById(windowId);
        if (window == null) {
            throw new BusinessException(ResultCode.WINDOW_NOT_FOUND);
        }
        
        // 验证队列状态
        if (!"CALLING".equals(queue.getStatus())) {
            throw new BusinessException(ResultCode.QUEUE_STATUS_ERROR);
        }
        
        // 验证窗口ID是否匹配
        if (!windowId.equals(queue.getWindowId())) {
            throw new BusinessException(ResultCode.WINDOW_MISMATCH);
        }
        
        // 更新状态为未到场
        LambdaUpdateWrapper<Queue> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.eq(Queue::getId, queue.getId())
                .set(Queue::getStatus, "NO_SHOW")
                .set(Queue::getEndTime, LocalDateTime.now());
        
        boolean result = update(updateWrapper);
        
        // 清除窗口当前票号
        if (result) {
            windowService.updateCurrentNumber(windowId, null);
            
            // 更新业务类型等待人数
            int waitingCount = getWaitingCountByServiceType(queue.getServiceTypeId());
            ServiceType serviceType = serviceTypeMapper.selectById(queue.getServiceTypeId());
            String serviceTypeName = serviceType != null ? serviceType.getName() : "";
            
            // 发布队列状态更新消息
//            publishQueueStatusUpdate(queue.getServiceTypeId(), serviceTypeName, null, waitingCount, 0);
        }
        statisticsService.updateWaitingCount(queue.getServiceTypeId());
        CurrentScreens.sendRefreshData();
        return result;
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean recall(String ticketNumber, Long windowId) {
        // 验证票号是否存在
        Queue queue = queueMapper.selectByTicketNumber(ticketNumber);
        if (queue == null) {
            throw new BusinessException(ResultCode.TICKET_NOT_FOUND);
        }
        
        // 验证窗口是否存在
        Window window = windowMapper.selectById(windowId);
        if (window == null) {
            throw new BusinessException(ResultCode.WINDOW_NOT_FOUND);
        }
        
        // 验证队列状态
        if (!"CALLING".equals(queue.getStatus())) {
            throw new BusinessException(ResultCode.QUEUE_STATUS_ERROR);
        }
        
        // 验证窗口ID是否匹配
        if (!windowId.equals(queue.getWindowId())) {
            throw new BusinessException(ResultCode.WINDOW_MISMATCH);
        }
        
        // 获取业务类型
        ServiceType serviceType = serviceTypeMapper.selectById(queue.getServiceTypeId());
        
        // 广播叫号消息
        broadcastCallMessage(queue, window, serviceType);
        
        return true;
    }
    
    @Override
    public QueueStatusResponse getQueueStatus(String ticketNumber) {
        // 验证票号是否存在
        Queue queue = queueMapper.selectByTicketNumber(ticketNumber);
        if (queue == null) {
            throw new BusinessException(ResultCode.TICKET_NOT_FOUND);
        }
        
        // 获取业务类型
        ServiceType serviceType = serviceTypeMapper.selectById(queue.getServiceTypeId());
        if (serviceType == null) {
            throw new BusinessException(ResultCode.BUSINESS_TYPE_NOT_FOUND);
        }
        
        // 获取窗口信息
        Window window = null;
        if (queue.getWindowId() != null) {
            window = windowMapper.selectById(queue.getWindowId());
        }
        
        // 统计等待人数
        int waitingCount = 0;
        List<String> ticketsAhead = new ArrayList<>();
        
        if ("WAITING".equals(queue.getStatus())) {
            // 获取同类型中排在前面的票号
            LambdaQueryWrapper<Queue> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Queue::getServiceTypeId, queue.getServiceTypeId())
                    .eq(Queue::getStatus, "WAITING")
                    .lt(Queue::getCreateTime, queue.getCreateTime())  // 创建时间早于当前票的
                    .orderByAsc(Queue::getCreateTime);
            
            List<Queue> aheadQueues = list(queryWrapper);
            waitingCount = aheadQueues.size();
            
            // 获取前面的票号列表
            ticketsAhead = aheadQueues.stream()
                    .map(Queue::getTicketNumber)
                    .collect(Collectors.toList());
        }
        
        // 计算预估等待时间
        int estimatedWaitTime = calculateEstimatedWaitTime(queue.getServiceTypeId(), waitingCount);
        
        // 构建响应
        QueueStatusResponse response = new QueueStatusResponse();
        response.setServiceTypeId(queue.getServiceTypeId());
        response.setServiceTypeName(serviceType.getName());
        response.setTicketNumber(ticketNumber);
        response.setStatus(queue.getStatus());
        response.setWaitingCount(waitingCount);
        response.setEstimatedWaitTime(estimatedWaitTime);
        response.setTicketsAhead(ticketsAhead);
        
        if (window != null) {
            response.setCurrentWindowNumber(window.getWindowNumber());
            response.setCurrentWindowName(window.getName());
        }
        
        response.setLastCallTime(queue.getCallTime());
        response.setUpdateTime(LocalDateTime.now());
        
        return response;
    }
    
    @Override
    public List<QueueVO> getWaitingQueues() {
        LambdaQueryWrapper<Queue> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Queue::getStatus, "WAITING");
        List<Queue> queueList = queueMapper.selectList(queryWrapper);
        return queueList.stream()
                .map(this::convertToVO)
                .collect(Collectors.toList());
    }
    
    @Override
    public int getWaitingCountByServiceType(Long serviceTypeId) {
        return queueMapper.countWaitingByServiceType(serviceTypeId);
    }
    
    @Override
    public QueueVO getCurrentQueueByWindow(Long windowId) {
        Queue queue = queueMapper.selectCurrentQueueByWindow(windowId);
        return convertToVO(queue);
    }
    
    @Override
    public PageResult<QueueVO> getQueueList(PageRequest pageRequest, String status, Long serviceTypeId) {
        IPage<Queue> page = new Page<>(pageRequest.getPageNum(), pageRequest.getPageSize());
        
        // 构建查询条件
        LambdaQueryWrapper<Queue> queryWrapper = new LambdaQueryWrapper<>();
        
        // 添加状态过滤
        if (StrUtil.isNotBlank(status)) {
            queryWrapper.eq(Queue::getStatus, status);
        }
        
        // 添加业务类型过滤
        if (serviceTypeId != null) {
            queryWrapper.eq(Queue::getServiceTypeId, serviceTypeId);
        }
        
        // 设置排序
        if (StrUtil.isNotBlank(pageRequest.getOrderBy())) {
            queryWrapper.orderBy(true, 
                    pageRequest.getIsAsc(), 
                    "create_time".equals(pageRequest.getOrderBy()) ? Queue::getCreateTime : 
                    "call_time".equals(pageRequest.getOrderBy()) ? Queue::getCallTime : 
                    Queue::getId);
        } else {
            queryWrapper.orderByDesc(Queue::getCreateTime);
        }
        
        // 执行分页查询
        page = page(page, queryWrapper);
        
        // 转换为VO
        List<QueueVO> voList = page.getRecords().stream()
                .map(this::convertToVO)
                .collect(Collectors.toList());
        
        // 构建分页结果
        return PageResult.<QueueVO>builder()
                .pageNum(pageRequest.getPageNum())
                .pageSize(pageRequest.getPageSize())
                .total(page.getTotal())
                .pages((int) page.getPages())
                .list(voList)
                .hasPrev(pageRequest.getPageNum() > 1)
                .hasNext(pageRequest.getPageNum() < page.getPages())
                .build();
    }
    
    @Override
    public QueueVO getByTicketNumber(String ticketNumber) {
        Queue queue = queueMapper.selectByTicketNumber(ticketNumber);
        return convertToVO(queue);
    }
    
    @Override
    public int calculateEstimatedWaitTime(Long serviceTypeId, int position) {
        // 获取服务类型下开放的窗口数
        List<Window> availableWindows = windowService.getAvailableWindowsByServiceType(serviceTypeId);
        int windowCount = availableWindows.size();
        
        // 使用平均服务时间计算
        if (windowCount <= 0) {
            windowCount = 1; // 至少有一个窗口
        }
        
        // 计算预估等待时间（分钟）
        return (position / windowCount + 1) * avgServiceTime;
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int resetAllQueueStatus() {
        // 查找所有未完成的队列
        LambdaQueryWrapper<Queue> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(Queue::getStatus, "WAITING", "CALLING", "SERVING");
        
        List<Queue> unfinishedQueues = list(queryWrapper);
        
        // 更新状态
        int count = 0;
        for (Queue queue : unfinishedQueues) {
            LambdaUpdateWrapper<Queue> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.eq(Queue::getId, queue.getId());
            
            if ("WAITING".equals(queue.getStatus())) {
                updateWrapper.set(Queue::getStatus, "NO_SHOW");
            } else {
                updateWrapper.set(Queue::getStatus, "COMPLETED");
            }
            
            updateWrapper.set(Queue::getEndTime, LocalDateTime.now());
            
            if (update(updateWrapper)) {
                count++;
            }
        }
        
        // 清除所有窗口的当前票号
        LambdaUpdateWrapper<Window> windowUpdateWrapper = new LambdaUpdateWrapper<>();
        windowUpdateWrapper.set(Window::getCurrentNumber, null);
        windowMapper.update(null, windowUpdateWrapper);
        
        return count;
    }
    
    /**
     * 生成票号
     */
    private String generateTicketNumber(ServiceType serviceType) {
        // 获取前缀
        String prefix = serviceType.getPrefix();

        // 构建Redis键: ticket:序列号:业务类型ID:当前日期
        String redisSequenceKey = "ticket:sequence:" + serviceType.getId() + ":" + LocalDate.now();

        // 检查Redis中是否已存在序列号，不存在则从数据库初始化
        Boolean hasKey = redisTemplate.hasKey(redisSequenceKey);
        if (Boolean.FALSE.equals(hasKey)) {
            // 从数据库获取当日最大票号
            String maxTicketNumber = queueMapper.selectMaxTicketNumberToday(serviceType.getId(), prefix);

            if (maxTicketNumber != null) {
                // 解析序号
                String sequenceStr = maxTicketNumber.substring(prefix.length());
                try {
                    int currentMaxSequence = Integer.parseInt(sequenceStr);
                    // 初始化Redis序列号为当前最大值
                    redisTemplate.opsForValue().set(redisSequenceKey, currentMaxSequence);
                    log.info("初始化票号序列号: key={}, value={}", redisSequenceKey, currentMaxSequence);
                } catch (NumberFormatException e) {
                    log.error("解析票号序列号失败: {}", maxTicketNumber, e);
                    // 解析失败，从0开始
                    redisTemplate.opsForValue().set(redisSequenceKey, 0);
                }
            } else {
                // 数据库中没有记录，从0开始
                redisTemplate.opsForValue().set(redisSequenceKey, 0);
            }
        }
        
        // 原子递增操作
        long sequence = redisTemplate.opsForValue().increment(redisSequenceKey);
        
        // 设置过期时间（确保第二天会重置序列号）
        redisTemplate.expire(redisSequenceKey, 24, TimeUnit.HOURS);
        
        // 格式化序号（3位数，不足补0）
        return prefix + String.format("%03d", sequence);
    }
    
    /**
     * 验证人脸特征
     *
     * @param image Base64编码的人脸图像
     * @param userId 用户ID
     * @return 验证结果
     */
    @Override
    public boolean verifyFaceFeature(String image, String userId) {
        log.info("开始验证人脸特征，用户ID：{}", userId);
        
        try {
            FaceVerificationRequest request = new FaceVerificationRequest();
            request.setUserId(Long.valueOf(userId));
            request.setImageBase64(image);
            request.setThreshold((double) similarityThreshold);
            
            Result<FaceVerificationResponse> result = faceProxyController.verifyFace(request);
            
            if (result != null && Objects.equals(result.getCode(), ResultCode.SUCCESS.getCode()) && result.getData() != null) {
                FaceVerificationResponse response = result.getData();
                boolean isVerified = response.isVerified();
                log.info("人脸特征验证{}，用户ID：{}，相似度：{}", isVerified ? "通过" : "失败", 
                        userId, response.getConfidence());
                return isVerified;
            } else {
                String errorMsg = result != null ? result.getMessage() : "验证结果为空";
                log.error("人脸特征验证失败，用户ID：{}，错误：{}", userId, errorMsg);
                throw new BusinessException(ResultCode.FACE_VERIFY_FAILED, "人脸特征验证失败：" + errorMsg);
            }
        } catch (ExternalServiceException e) {
            log.error("调用人脸识别服务失败，用户ID：{}", userId, e);
            // 保持原始异常类型和详细信息，让全局异常处理器处理
            throw e;
        } catch (Exception e) {
            log.error("人脸特征验证过程中发生异常，用户ID：{}", userId, e);
            throw new BusinessException(ResultCode.FACE_VERIFY_FAILED, "人脸特征验证过程中发生异常：" + e.getMessage());
        }
    }

    @Override
    public List<QueueVO> getQueueHistoryByWindowId(Long windowId) {
        IPage<Queue> page = new Page<>(1, 10);
        // 构建查询条件
        LambdaQueryWrapper<Queue> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(Queue::getStatus, "COMPLETED", "NO_SHOW")
                .eq(Queue::getWindowId, windowId)
                .orderByDesc(Queue::getEndTime);
        queueMapper.selectList(queryWrapper);
        // 执行分页查询
        page = page(page, queryWrapper);

        // 构建分页结果
        return page.getRecords().stream()
                .map(this::convertToVO)
                .collect(Collectors.toList());
    }

    @Override
    public Queue getCurrentQueue() {
        // 构建查询条件
        LambdaQueryWrapper<Queue> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Queue::getStatus, "CALLING")
                .orderByDesc(Queue::getCallTime);
        List<Queue> queues = queueMapper.selectList(queryWrapper);
        if (queues.isEmpty()) {
            return null;
        } else {
            return queues.get(0);
        }
    }

    @Override
    public List<Queue> geTodayQueue() {
        LambdaQueryWrapper<Queue> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.ge(Queue::getCreateTime, LocalDate.now().toString())
                .orderByDesc(Queue::getEndTime);
        return queueMapper.selectList(queryWrapper);
    }

    /**
     * 广播叫号消息
     */
    private void broadcastCallMessage(Queue queue, Window window, ServiceType serviceType) {
        if (queue == null || window == null || serviceType == null) {
            return;
        }
        
        // 构建叫号消息
        CallMessage callMessage = CallMessage.builder()
                .type(Constants.WebSocketMessageType.CALL_MESSAGE)
                .queueId(queue.getId())
                .queueNumber(queue.getTicketNumber())
                .windowId(window.getId())
                .windowNumber(window.getWindowNumber().toString())
                .windowName(window.getName())
                .businessTypeId(serviceType.getId())
                .businessTypeName(serviceType.getName())
                .callTime(LocalDateTime.now())
                .status(queue.getStatus())
                .callCount(1) // 叫号次数
                .build();
        
        // 广播到叫号大屏
        callScreenWebSocketHandler.broadcastCallMessage(callMessage);
    }
    
    /**
     * 发布队列状态更新消息
     */
    private void publishQueueStatusUpdate(Long businessTypeId, String businessTypeName, 
                                         String currentQueueNumber, int waitingCount, int estimatedWaitingTime) {
        // 构建队列状态消息
        QueueStatusMessage message = QueueStatusMessage.builder()
                .type(Constants.WebSocketMessageType.STATUS_UPDATE)
                .businessTypeId(businessTypeId)
                .businessTypeName(businessTypeName)
                .waitingCount(waitingCount)
                .estimatedWaitingTime(estimatedWaitingTime)
                .updateTime(new DateTime())
                .build();
        
        if (currentQueueNumber != null) {
            message.setQueueNumber(currentQueueNumber);
        }
        
        // 获取近期叫号的票号列表
        LambdaQueryWrapper<Queue> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Queue::getServiceTypeId, businessTypeId)
                .in(Queue::getStatus, "CALLING", "SERVING")
                .orderByDesc(Queue::getCallTime)
                .last("LIMIT 5");
        
        List<Queue> recentCalledQueues = list(queryWrapper);
        List<String> recentCalledQueueNumbers = recentCalledQueues.stream()
                .map(Queue::getTicketNumber)
                .collect(Collectors.toList());
        
        message.setRecentCalledQueueNumbers(recentCalledQueueNumbers);
        
        // 广播状态更新消息
        queueStatusWebSocketHandler.broadcastQueueStatusMessageToBusinessType(message);
        
        // 缓存当前业务类型的状态
        String cacheKey = "queue:status:" + businessTypeId;
        redisTemplate.opsForValue().set(cacheKey, message, 1, TimeUnit.DAYS);
    }
    
    /**
     * 发送叫号消息到MQ
     */
    private void sendCallMessage(Queue queue, Window window) {
        if (queue == null || window == null) {
            return;
        }
        
        // 构建MQ消息
        CallMessage callMessage = CallMessage.builder()
                .queueId(queue.getId())
                .queueNumber(queue.getTicketNumber())
                .windowId(window.getId())
                .windowNumber(window.getWindowNumber().toString())
                .windowName(window.getName())
                .callTime(LocalDateTime.now())
                .build();
        
        // 发送到叫号MQ
        try {
            rabbitTemplate.convertAndSend(Constants.MQConstants.EXCHANGE_DIRECT,
                    Constants.MQConstants.ROUTING_KEY_CALL, callMessage);
        } catch (Exception e) {
            log.error("发送叫号MQ消息失败", e);
        }
    }
    
    /**
     * 将队列实体转换为视图对象
     */
    private QueueVO convertToVO(Queue queue) {
        if (queue == null) {
            return null;
        }
        
        QueueVO vo = new QueueVO();
        BeanUtil.copyProperties(queue, vo);
        
        // 获取业务类型名称
        if (queue.getServiceTypeId() != null) {
            ServiceType serviceType = serviceTypeMapper.selectById(queue.getServiceTypeId());
            if (serviceType != null) {
                vo.setServiceTypeName(serviceType.getName());
            }
        }
        
        // 获取窗口信息
        if (queue.getWindowId() != null) {
            Window window = windowMapper.selectById(queue.getWindowId());
            if (window != null) {
                vo.setWindowNumber(window.getWindowNumber());
                vo.setWindowName(window.getName());
            }
        }
        
        return vo;
    }
    
    /**
     * 创建取号响应
     * 
     * @param queue 队列实体
     * @param serviceType 业务类型实体
     * @param waitingCount 等待人数
     * @return 取号响应
     */
    private TakeNumberResponse createTakeNumberResponse(Queue queue, ServiceType serviceType, Integer waitingCount) {
        // 预估等待时间 = 等待人数 * 平均服务时间
        int estimatedWaitingTime = waitingCount * avgServiceTime;
        
        return TakeNumberResponse.builder()
                .ticketNumber(queue.getTicketNumber())
                .serviceTypeId(serviceType.getId())
                .serviceTypeName(serviceType.getName())
                .waitingCount(waitingCount)
                .estimatedWaitingTime(estimatedWaitingTime)
                .createTime(queue.getCreateTime().toString())
                .build();
    }
} 